home *** CD-ROM | disk | FTP | other *** search
/ PC Answers 1995 May / PC Answers CD-ROM 7 (Future Publishing) (May 1995).iso / vbits / code / mee / vbdao / vbdao.bas < prev   
Encoding:
BASIC Source File  |  1994-10-06  |  12.9 KB  |  385 lines

  1.  
  2. ' AdvancedPassthrough
  3. '
  4. ' Using Jet 2.0, you can use quickly and easily retrieve
  5. ' multiple result sets and warnings and/or print statements
  6. ' from stored procedures.
  7. ' See the MS Access 2.0 help topic "Creating a Passthrough Query"
  8. ' for more information on the details of what is presented here.
  9. '
  10. Sub advancedpassthrough ()
  11.  
  12. ' Scenario: You have a stored procedure that returns
  13. ' multiple results sets that you need to use.
  14.  
  15. ' Solution: Using Access 2.0, first create a query that
  16. ' executes the stored procedure.  Open the "pubs.mdb"
  17. ' database in Access and examine the query called:
  18. '   "Run: Multiple Result Set Sproc"
  19. ' To see and/or create the corresponding sproc on SQL
  20. ' server, look at its companion passthrough query:
  21. '   "Create: Multiple Result Set Sproc"
  22. '
  23. ' You cannot create stored Jet passthrough queries from VB3
  24. ' because DAO lacks the necessary properties (it is
  25. ' possible in Access 2' DAO).
  26.  
  27. ' If you simply execute the stored procedure, you will get
  28. ' a snapshot with the first result set returned.
  29.  
  30. Dim d As Database
  31. Set d = OpenDatabase("pubs.mdb")
  32.  
  33. Dim s As Snapshot
  34.  
  35. Set s = d.CreateSnapshot("Run: Multiple Result Set Sproc")
  36.  
  37. Debug.Print s(0).Name, s(1).Name, s(2).Name
  38. Debug.Print s(0), s(1), s(2)
  39.  
  40. ' Depending on your scenario, that may be all you need.
  41. ' However, you may wish to get the other result sets.
  42. ' You can do this by running a maketable query version
  43. ' of the stored procedure as follows:
  44.  
  45. ResultTable = "Results"
  46. d.Execute "SELECT * INTO " & ResultTable & " FROM [Run: Multiple Result Set Sproc];"
  47.  
  48. ' This will create a set of (local) tables,
  49. '       Results, Results1 ...
  50. ' with each table containing successive returned result
  51. ' sets.  We can see this by listing them:
  52.  
  53. Debug.Print d.TableDefs(ResultTable).DateCreated
  54. Debug.Print d.TableDefs(ResultTable & "1").DateCreated
  55.  
  56. ' These can then be opened like any normal table or bound
  57. ' to a data control or whatever.
  58. ' When done with them, you can delete them in preparation
  59. ' for running the stored proc again.
  60.  
  61. d.TableDefs.Delete ResultTable
  62. d.TableDefs.Delete ResultTable & "1"
  63.  
  64. ' Another thing that stored procedures sometimes do is
  65. ' use the Transact SQL "Print" command to return misc
  66. ' data.  You can use those from Jet 2.0 also.  The msgs
  67. ' will get written to a new table called "<username> - N".
  68. ' Thus:
  69.  
  70. Set s = d.CreateSnapshot("Run: Sproc w/ Print Statements")
  71.  
  72. ' (Note: created with "Create: Sproc w/ Print Statements")
  73.  
  74. d.TableDefs.Refresh
  75. Debug.Print d.TableDefs("Admin - 00").DateCreated
  76.  
  77. ' Note that the table is only created on demand.  If the
  78. ' print statement didn't occur until after all the results
  79. ' had been returned, then we would have to close the
  80. ' snapshot before they appeared.
  81.  
  82. s.Close
  83. d.Close
  84.  
  85. End Sub
  86.  
  87. ' Minimize Connections
  88. '
  89. ' When working with ODBC data,  it can help to be aware of your
  90. ' connection usage.  Although Jet quietly manages these for
  91. ' you, you can tweak its handling with some of the tips below.
  92. '
  93. '   + Preconnect at app startup to save time later
  94. '   + limit returned dynasets to 100 records
  95. '   + force connection release by completing dynaset filling
  96. '   + modify the ConnectionTimeout in [ODBC] section
  97. '   + Remember, connections can't be closed if:
  98. '      - a transaction is pending
  99. '      - not all query results have been fetched
  100. '
  101. Sub MinimizeConnections ()
  102.  
  103. ' When you first reference an attached table, Jet will make a
  104. ' connection to the ODBC database.  Since creating the
  105. ' connection can be an expensive operation which causes a
  106. ' pause at a bad time, you may wish to force it to be opened
  107. ' at some earlier time.  The following code will force this:
  108.  
  109. Dim d As Database, ConnStr As String
  110.  
  111. ConnStr = "ODBC;"       ' prompt the user for info
  112. Set d = OpenDatabase("", False, False, ConnStr)
  113. ConnStr = d.Connect     ' save the filled in string for later
  114. d.Close
  115.  
  116. ' Note that your application should not use the database that
  117. ' you've opened above -- it should use attached tables.  The
  118. ' connection created above will last until it is timed out
  119. ' (default is 10 mins).
  120.  
  121. End Sub
  122.  
  123. ' Use parameterized queries instead of 'dynamic SQL'.  Why?
  124. '   + they execute faster
  125. '   + they're easier to use than building SQL strings
  126. '
  127. Sub UseParameterizedQueries ()
  128.  
  129. ' Say you wished to repeatedly find a set of records based
  130. ' on certain criteria.  For example, say you wanted all
  131. ' Authors whose name started with a certain letter.  One way
  132. ' to do this is to have code like:
  133.  
  134. Dim d As Database, ds As dynaset
  135. Set d = OpenDatabase("biblio.mdb")
  136. firstletter = "G"
  137.  
  138. sqlstr = "Select * from Authors where Author like """ & firstletter & "*"""
  139.  
  140. Set ds = d.CreateDynaset(sqlstr)
  141. Debug.Print ds!Author
  142. ds.Close
  143.  
  144. 'A preferred way is to have a parameterized query already created
  145. 'and just set the parameter appropriately.  For example:
  146.  
  147. 'I'll create the query here so that you can use standard biblio.
  148. 'Normally it would already exist in the database
  149.  
  150. Dim qd As querydef
  151. Set qd = d.CreateQueryDef("AuthorLike", "PARAMETERS FirstLetter Text; SELECT DISTINCTROW Authors.Au_ID, Authors.Author FROM Authors WHERE ((Authors.Author Like [FirstLetter]))")
  152. qd.Close
  153.  
  154. ' This is where the code starts that you would normally write
  155.  
  156. Set qd = d.OpenQueryDef("AuthorLike")
  157. qd!firstletter = firstletter & "*"
  158. Set ds = qd.CreateDynaset()
  159.  
  160. Debug.Print ds!Author
  161. ds.Close
  162.  
  163. ' As you can see, the code is shorter, easier to understand,
  164. ' easier to maintain -- and, actually runs faster as well!
  165.  
  166. qd.Close                            ' tidy up after demo
  167. d.DeleteQueryDef ("AuthorLike")
  168. End Sub
  169.  
  170. ' Its best to use SQL statements whereever possible rather
  171. ' than explicit coding.  Why?
  172. '  + the query engine has tighter binding to the actual isam
  173. '    than VB code
  174. '  + the query engine has access to certain optimized operations
  175. '    such as bulk copy operations
  176. '  + SQL statements are quicker and easier to debug than the
  177. '    equivalent code
  178. '
  179. Sub usesqlstatements ()
  180.  
  181. ' A simple example is the following:
  182.  
  183. ' bad code
  184. Dim d As Database
  185. Dim t As table
  186.  
  187. Set d = OpenDatabase("biblio.mdb")
  188. Set t = d.OpenTable("Titles")
  189.  
  190. BeginTrans      ' faster with a transaction around it
  191. While Not t.EOF
  192.     t.Edit
  193.     t![Year Published] = t![Year Published] - 100
  194.     t.Update
  195.     t.MoveNext
  196. Wend
  197. Rollback        ' added to keep the sample data intact
  198.   
  199. ' Good code
  200. BeginTrans
  201.     d.Execute "Update Titles Set [Year Published] = [Year Published] - 100"
  202. Rollback    ' only rolling back so the demo works repeatedly
  203.  
  204. ' There are some caveats to note.
  205. '
  206. '   If an error occurs on an individual record, the rest of
  207. '   the updates will still occur and you won't know that
  208. '   an error occured.  In Access 2, this problem is avoided
  209. '   by using the DB_FAILONERROR flag as an Execute option.  To
  210. '   detect it in VB3, you need to get creative with things like:
  211.  
  212. Dim dsTemp As dynaset
  213.  
  214. BeginTrans      ' This is so I can rollback to maintain the data
  215.  
  216.     BeginTrans
  217.     d.Execute "Update Titles Set [Year Published] = [Year Published] - 100"
  218.     Set dsTemp = d.CreateDynaset("Select count(*) from Titles where [Year Published] > 1950")
  219.     If dsTemp(0) <> 0 Then
  220.         Rollback
  221.     Else
  222.         CommitTrans
  223.     End If
  224.  
  225. Rollback        ' Again, this line not normally used!
  226.  
  227. ' What I did above was to measure the success of the Execute.
  228. ' If the count(*) had come back > 0, then I would have known
  229. ' that one of the records hadn't been updated, due say, to it
  230. ' being locked by someone else.  Obviously you'll have to get
  231. ' creative for your own application.  Since Count(*) tends to
  232. ' be very fast, especially in Jet 2.0, this probably still
  233. ' works out faster than doing explicit coding.
  234.  
  235. ' Another caveat is that you don't know how many records were
  236. ' modified, which is something that is often useful to give
  237. ' to users.  Again you can solve this with some extra SQL
  238. ' queries, and again, Access 2 solves this with the new
  239. ' "RowsAffected" property that you can read after the .Execute
  240. ' method completes.
  241.  
  242. End Sub
  243.  
  244. ' It is best to use transactions where possible.  Why?
  245. '
  246. '   + To decrease programming complexity
  247. '   + Around bulk updates to increase speed
  248. '   + Around query result sets to increase concurrency
  249. '
  250. Sub UseTransactions ()
  251.  
  252. ' + To decrease programming complexity
  253. ' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  254. ' In databases without transactions, its common to simulate
  255. ' them by making extensive use of temporary tables and variables.
  256. ' This just isn't as necessary in Jet.  By combining several
  257. ' operations within a transaction, you can abort the whole
  258. ' group just by issuing a rollback.
  259.  
  260. ' For example, the following code adds a book to the database
  261. ' that has a new author.  Rather than get the author's
  262. ' information one at a time from the user and then store it
  263. ' in a temp table, similarly get the title and then confirm
  264. ' with the user that everything is ok and then make the
  265. ' actual changes, these can all be done immediately and
  266. ' then aborted if need be.
  267.  
  268. Dim d As Database, tAuthors As table, tTitles As table
  269. Dim au_id As Long
  270.  
  271. Set d = OpenDatabase("biblio.mdb")
  272. Set tAuthors = d.OpenTable("Authors")
  273. Set tTitles = d.OpenTable("Titles")
  274.  
  275. BeginTrans      ' indented code to show transaction
  276.  
  277.     tAuthors.AddNew
  278.     tAuthors!Author = "Austen, Jane"
  279.     tAuthors.Update
  280.     tAuthors.Bookmark = tAuthors.LastModified
  281.     au_id = tAuthors!au_id
  282.     
  283.     tTitles.AddNew
  284.     tTitles!Title = "Pride and Prejudice"
  285.     tTitles![Year Published] = 1845
  286.     tTitles!au_id = au_id1
  287.     tTitles!ISBN = "123-4567-12345"
  288.     tTitles.Update
  289.  
  290. ' at this stage, before completing the transaction, you can
  291. ' look at the database and confirm that the data appears there
  292. ' however, by executing the following statement, it will be
  293. ' automatically removed from both tables.  This can be shown
  294. ' by printing the last record of the table before and after the
  295. ' rollback as follows:
  296.  
  297. tAuthors.MoveLast
  298. Debug.Print tAuthors!Author     ' prints "Austen, Jane"
  299.  
  300. Rollback
  301.  
  302. tAuthors.MoveLast
  303. Debug.Print tAuthors!Author     ' prints "Wiederhold, Gio."
  304.  
  305.  
  306. ' You can also nest transactions so that portions of an
  307. ' operation can be aborted without affecting other operations.
  308. ' For example you could confirm and commit the author and
  309. ' title information separately in nested transactions and still
  310. ' rollback the containing transaction - as follows:
  311.  
  312. BeginTrans      ' outer transaction
  313.  
  314.     BeginTrans
  315.         tAuthors.AddNew
  316.         tAuthors!Author = "Austen, Jane"
  317.         tAuthors.Update
  318.         tAuthors.Bookmark = tAuthors.LastModified
  319.         au_id = tAuthors!au_id
  320.     CommitTrans
  321.     
  322.     BeginTrans
  323.         tTitles.AddNew
  324.         tTitles!Title = "Pride and Prejudice"
  325.         tTitles![Year Published] = 1845
  326.         tTitles!au_id = au_id1
  327.         tTitles!ISBN = "123-4567-12345"
  328.         tTitles.Update
  329.     CommitTrans
  330.  
  331. Rollback
  332.  
  333. ' Although this may not seem especially useful in this trivial
  334. ' example, you can imagine how nested independent subroutines
  335. ' would find this technique extremely powerful.
  336.  
  337. ' + Around bulk updates to increase speed
  338. ' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  339. ' If you're doing a large number of updates, you can group
  340. ' the updates within a transaction for speed.  Without
  341. ' transactions, every update is written to disk when its
  342. ' completed.  Updates inside a transaction are only written
  343. ' to disk when the transaction is completed.
  344. '
  345. ' Note carefully the tradeoff however.  After the number of
  346. ' uncommitted transactions exceeds available memory, they will
  347. ' start being spooled to temporary storage on disk.  When you
  348. ' finally commit, they will first have to be read and deleted
  349. ' from temporary storage and then written to the actual tables.
  350. '
  351. ' Of course the best way to do bulk updates is to use SQL
  352. ' statements like UPDATE and INSERT INTO (which already have
  353. ' internal transactions, so there's no benefit in placing them
  354. ' inside a transaction.
  355.  
  356. ' + Around query result sets to increase concurrency
  357. ' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  358. ' Whenever you run a query that reads an index to find results,
  359. ' so-called "read locks" will be placed on that index so that
  360. ' other users don't update the locks beneath the query scan.
  361. ' For performance reasons, those locks time out during idle
  362. ' time (or when FreeLocks is called).  However, you can force
  363. ' faster timeout of the readlocks by writing code as follows:
  364.  
  365. Dim dsResults As dynaset
  366. BeginTrans
  367.     Set dsResults = d.CreateDynaset("By State")
  368. CommitTrans
  369.  
  370. While Not dsResults.EOF
  371.    ' do something
  372.    dsResults.MoveNext
  373. Wend
  374.  
  375. ' When you are using the database single user, this will actually
  376. ' slow down your program.  Similarly if its unlikely that anyone
  377. ' else be writing to the same table as you're querying, you should
  378. ' also not do this.  However, if you know that other people will
  379. ' be updating the table that you are running a query against,
  380. ' this will unblock them faster.
  381.  
  382. End Sub
  383.  
  384.